
// 自己写的阻塞队列
// 此处不考虑 泛型 , 直接用int来表示元素类型了
// 要想实现一个阻塞队列,需要先写一个普通队列(可以基于数组,也可以基于链表(很容易进行头删和尾插(时间复杂度: o1  和 O(1) 可以是"O(1)")))
class MyBlockingQueue{
        // 接下来基于数组的方式来实现阻塞队列 (用循环队列的方式来)
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    // 入队列
    public void put(int value) throws InterruptedException {
        synchronized (this) {  // 在这里面要进行线程安全操作
        /*    if(size == items.length) {
               // return; // 要考虑数组是否慢了

                // 在考虑阻塞的问题中呢 ,我们并不希望直接return
                // 而是进行阻塞
                this.wait();
            } // 这里的if呢还是需要修改一次的 要改成while
*/

            while(size == items.length) {
                this.wait();
            }

            //插入
            items[tail] = value;
            tail++; // 但是 tail 操作是要进行循环的 , 是要走到开头的
            // 接下来对 tail 处理
            // 第一种写法
            // tail = tail % items.length; // 这里面是 (tail + 1) % items.length; // 但其实上面的tail 先 ++ 了一次
            // 第二种写法
            if(tail >= items.length){
                tail = 0;
            }

            // 此时这两种方法 哪种更好呢?
            // 其实 个人偏向是 第二种方法
            // 因为 代码是写给人来看的 第一种很容易不知道代码要做的意思
            // 而且这个代码的效率可能要比%更高

            size++;

            // 这个 notify 是来唤醒 take 中的 wait
            this.notify();
        }
    }
    // 出队列
    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this) {// 在这里面要进行线程安全操作    此处的 synchronized 是包裹了整个代码, 其实 加到方法上也是可以的
            // 先判断出不了的情况
//            if(size == 0){
//           //     return null;
//
//                // 这时的队列如果为空呢也应该阻塞
//                this.wait();  // 这个wait 由 put 操作来唤醒
//            }  // 同理 这里的if 也是要改的

            while(size == 0){
                this.wait();
            }

            // 接下来进行出队列
            result = items[head];
            head++;
            // head 也是需要循环的
            if(head>=items.length) {
                head = 0;
            }
            size--;

            // 唤醒put 中的 wait
            this.notify(); // 这里之所以不用sleep是因为我们并不知道线程要等多久
        }
        return result;
    }

}
// 上面的两个wait 操作呢是不可能同时触发的,因为this 指的是同一个对象,一个队列是不可能即是满又是空的

// 而且 当 wait 被唤醒的时候,此时if的条件,一定就不成立么?
// 具体来说 , 例如 put 中的 wait 被唤醒,要求队列不满
// 但是 wait 被唤醒之后 队列一定是不满的么?
// 注意 当前代码中 ,确实不会出现这种情况,当前的代码一定是 取元素成功 之后才唤醒.每次取元素都会唤醒
// 但是稳妥起见,最好的办法 是 wait 返回之后再次判定一下,此时的条件是不是具备了 !!!

public class ThreadBlockQueueRealize {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();
        /*queue.put(1);
        queue.put(2);
        queue.put(3);
        queue.put(4);
        queue.put(5);
        int result = 0;
        result = queue.take();
        System.out.println("result = "+result);
        result = queue.take();
        System.out.println("result = "+result);
        result = queue.take();
        System.out.println("result = "+result);
        result = queue.take();
        System.out.println("result = "+result);
        result = queue.take();
        System.out.println("result = "+result);
        // 插入五个元素也要打印五个元素

        // 当前我们已经完成了 普通队列 的实现
        // 接下来要加上阻塞功能

        // 阻塞功能就意味着, 队列 要在多线程环境下使用
        // 先要保证线程安全*/

        // 接下来 再写一个生产者和消费者问题
        Thread customer = new Thread(() -> {
            while(true){
                try {
                    int result = queue.take();
                    System.out.println("消费: "+result);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        customer.start();

        Thread producer = new Thread(() -> {
            int count = 0;
            while(true){
                try {
                    System.out.println("生产: "+count);
                    queue.put(count);
                    count++;

                  //  Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        producer.start();
    }
}
